// 对象转换成查询字符串方法
function queryStringify(obj){
    let str = '';

    for(let k in obj){
        str += k + '=' + obj[k] + '&';
    }

    return str.slice(0,-1);
}




// 封装 ajax 函数，测试调用
/* 
    封装 ajax 函数
    目的：为了发送 ajax 请求，向服务器获取数据
          即完成了 ajax 的标准四步
    使用：给开发人员使用
*/


// ajax 函数调用
// ajax({
//     url: 'xxx',
    // method: 'GET',
    // async: false,
    // data: {username, password, rpassword, nickname},
    // headers: {},
    // dataType: 'json',
    // success: function(){}
// })

/* 
    说明：
    参数 : 是否必填 : 要求传参格式 : 默认值
    1. 请求地址 url : 必填 : http://... : 无
    2. 请求方式 method : 选填 : undefined/GET/POST : GET
    3. 是否异步 async : 选填 : undefined/true/false : true
    4. 带给后端的数据 data : 选填 : undefined/字符串/对象 : 空字符串
    5. 请求头信息 headers : 选填 : undefined/对象 : { 'content-type': 'application/x-www-form-urlencoded' }
    6. 是否解析响应体 dataType : 选填 : undefined/'string(不将 xhr.responseText 从 JSON 转换成 对象)/'json(转换)' : 'string'
    7. 整个请求过程成功以后的函数，执行响应数据的输出方式 或 响应数据的状态码等 : 选填 : undefined/函数 : 空函数
*/

// ajax 函数
function ajax(options = {}){
    // 0-1. options 是一个对象，传递了本次请求所有相关信息，且设置了参数默认值
    // console.log('options：', options);
    
    // 0-2. 用解构赋值，从 options 内拿到所有的信息，后面使用方便
    // 等价于 const url = options.url ... ...
    const {url, method, async, data, headers, dataType, success} = options;


    // 1. 验证 options 内的信息：把不符合要求的信息踢出

    // 1-1. 验证 url(请求地址) 信息，url 必填
    if(!url){
        throw new Error('url 为必填选项，请传递此内容');
    }

    // 1-2. 验证 method(请求方式) 信息，method 选填
    // 不填是 undefined，填了必须是 GET 或者 POST
    if(!(method === undefined || /^(get|post)$/i.test(method))){
        // 代码来到这里，说明 method 不符合要求
        throw new Error('method 为选填，且只接受 "GET" 或者 "POST"');
    }

    // 1-3. 验证 async(是否异步) 信息，async 选填
    // 不填是 undefined，填了必须是 true 或者 false
    if(!(async === undefined || typeof async === 'boolean')){
        // 代码来到这里，说明 async 不符合要求
        throw new Error('async 为选填，且只接受布尔值');
    }

    // 1-4. 验证 data(带给后端的数据) 信息，data 选填
    // 不填是 undefined，填了必须是 字符串 或者 对象
    if(!(data === undefined || typeof data === 'string' || Object.prototype.toString.call(data) === '[object Object]')){
        // 代码来到这里，说明 data 不符合要求
        throw new Error('data 为选填，且接受字符串或者对象');
    }

    // 1-5. 验证 headers(请求头信息) 信息，headers 选填
    // 不填是 undefined，填了必须是 对象
    if(!(headers === undefined || Object.prototype.toString.call(headers) === '[object Object]')){
        throw new Error('headers 为选填，且只接受 对象');
    }

    // 1-6. 验证 dataType(是否解析响应体)，选填
    // 不填是 undefined，填了必须是字符串'string'(表示不解析) 或者 'json'(表示解析);
    if(!(dataType === undefined || /^(string|json)$/i.test(dataType))){
        throw new Error('dataType 为选填，且只接受 "json" 或者 "string"');
    }

    // 1-7. 验证 success(整个请求过程成功以后的函数，执行响应数据的输出方式 或 响应数据的状态码等)，选填
    // 不填是 undefined，填了必须是一个函数
    if(!(success === undefined || typeof success === 'function')){
        throw new Error('success 为选填，且只接受 函数');
    }


    // 2. 准备一套默认值，将默认值写在一个对象里
    // 同时也说明了，如果输入的不是默认值，会得到什么值

    const _default = {
        // 2-1. url(请求地址) 默认值
        // 代码来到这里，url肯定有内容
        url: url,

        // 2-2. method(请求方式) 默认值
        // 代码来到这里，method === undefined 或 get 或 post
        // method === undefined，默认值是 GET；metnod === get 或 post，是哪个就赋值哪个
        method: method || 'GET',

        // 2-3. async(是否异步) 默认值
        // 代码来到这里，async === undefined 或 true 或 false
        // async === true 或 false，就用布尔值；async === undefined，默认值是true
        async: typeof async === 'boolean' ? async : true,
        // async: async ?? true,

        // 2-4. data(带给后端的数据) 默认值
        // 代码来到这里，data === undefined 或 字符串 或 对象
        // data === undefined，默认值是空字符串，data === 字符串或对象，就用data
        data: data || '',

        // 2-5. headers(请求头信息) 默认值
        // 代码来到这里，headers === undefined 或 对象
        // headers === undefined，默认值是 { 'content-type': 'application/x-www-form-urlencoded' }
        // 如果 headers === { 'authorization': 'asdjhgasdg'(token) }，表示在默认值的基础上加上这个
        // 如果 headers === { 'content-type': 'application/json', 'authorization': 'asdjhgasdg'(token) }
        // 表示把默认值的 content-type 修改，并加上一个 authorization 属性
        // 实现以上功能，用 展开运算符 (...)
        headers: {'content-type': 'application/x-www-form-urlencoded', ...headers},

        // 2-6. dataType(是否解析响应体) 默认值
        // 代码来到这里，dataType === undefined 或 'json' 或 'string'
        // dataType === undefined，默认值是 'string'(不解析)
        // dataType === 'string' 或 'json'，是什么就赋值什么
        dataType: dataType || 'string',

        // 2-7. 验证 success(整个请求过程成功以后的函数，执行响应数据的输出方式 或 响应数据的状态码等) 默认值
        // 代码来到这里，success === undefined 或 函数
        // success === undefined，默认值是 空函数
        // success === 函数，是什么就赋值什么
        success: success || function(){}
    }

    // 2-8. 调整 data 的值
    // 当 data 是 对象 时，要转换成 查询字符串
    // 代码来到这里，_default.data === 字符串(包括空字符串) 或 对象
    if(typeof _default.data === 'object'){
        _default.data = queryStringify(_default.data);
    }

    // 2-9. 调整 url 的值
    // 如果是 get 请求，且 _default.data 有内容，url 要拼接 _default.data
    // 代码执行到这里，_default.data === 字符串 或 空字符串
    if(/^get$/i.test(_default.method) && _default.data){
        // 代码执行到这里，说明 method 是 get 请求，且 _default.data 有内容
        _default.url += '?' + _default.data;
    }


    // 3. 发送请求

    // 3-1. 创建 ajax 对象
    const xhr = new XMLHttpRequest()

    // 3-2. 配置 请求信息(用默认值)
    xhr.open(_default.method, _default.url, _default.async)

    // 3-4. 获取 响应数据
    xhr.onload = function(){
        // 判断 响应数据 是否需要解析
        const res = _default.dataType === 'string' ? xhr.responseText : JSON.parse(xhr.responseText);

        // 整个请求过程成功以后的函数，规定了响应数据的输出方式
        // 调用 _default.success 函数
        // 这里将 响应数据 当作 实参 传入
        // 在 html 文件中，请求接口时候写 形参 部分
        _default.success(res);   
    }
     
    // 设置请求头
    // 只要 headers 内有内容，就设置到请求头上
    // _default.headers 内有多少个信息，就设置多少条请求头
    for(let k in _default.headers){
        // k 是 content-type 或 authorization
        // _default.headers[k] 是 application/json(那三个) 或 'asdjhgasdg'(token)
        xhr.setRequestHeader(k, _default.headers[k]);
    }


    // 3-3. 发送请求
    // 判断是 get 还是 post
    /^get$/i.test(_default.method) ? xhr.send() : xhr.send(_default.data);

    // 运行此 ajax 函数，就相当于执行了 ajax 的四步
    // console.log('按照 ajax 四步进行后，发送的请求');
}






// 使用 Promise 语法，对 ajax 进行二次封装

// 将 ajax 封装函数 变成 Promise 对象，再封装进 pAjax 函数内
// 每次调用 pAjax 函数，就会生成一个 Promise 对象，对象里会再调用 ajax 函数，完成 请求过程
function pAjax(options = {}) {
    return new Promise((resolve, reject) => {
      ajax({
        url: options.url,
        data: options.data,
        async: options.async,
        method: options.method,
        headers: options.headers,
        dataType: options.dataType,
        success (res) {
          resolve(res)
        }
      })
    })
  }